<?php
declare(strict_types=1);

/**
 * MineAdmin is committed to providing solutions for quickly building web applications
 * Please view the LICENSE file that was distributed with this source code,
 * For the full copyright and license information.
 * Thank you very much for using MineAdmin.
 *
 * @Author X.Mo<root@imoi.cn>
 * @Link   https://gitee.com/xmo/MineAdmin
 */

namespace Mine\Office\Excel;

use Mine\Exception\MineException;
use Mine\Office\ExcelPropertyInterface;
use Mine\Office\MineExcel;
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Color;

class PhpOffice extends MineExcel implements ExcelPropertyInterface
{

    /**
     * 导入
     * @param \Mine\MineModel $model
     * @param \Closure|null $closure
     * @return bool
     * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
     * @throws \Psr\Container\ContainerExceptionInterface
     * @throws \Psr\Container\NotFoundExceptionInterface
     */
    public function import(\Mine\MineModel $model, ?\Closure $closure = null): bool
    {
        $request = container()->get(\Mine\MineRequest::class);
        $data = [];
        if ($request->hasFile('file')) {
            $file = $request->file('file');
            $tempFileName = 'import_'.time().'.'.$file->getExtension();
            $tempFilePath = BASE_PATH . '/runtime/'. $tempFileName;
            file_put_contents($tempFilePath, $file->getStream()->getContents());
            $reader = IOFactory::createReader(IOFactory::identify($tempFilePath));
            $reader->setReadDataOnly(true);
            $sheet = $reader->load($tempFilePath);
            $endCell = isset($this->property) ? $this->getColumnIndex(count($this->property)) : null;
            try {
                foreach ($sheet->getActiveSheet()->getRowIterator(2) as $row) {
                    $temp = [];
                    $propertyIndex = 0;
                    foreach ($row->getCellIterator('A', $endCell) as $index => $item) {
                        if (isset($this->property[$propertyIndex])) {
                            $temp[$this->property[$propertyIndex]['name']] = $item->getFormattedValue();
                        }

                        $propertyIndex ++;
                    }
                    if (! empty($temp)) {
                        $data[] = $temp;
                    }
                }
                unlink($tempFilePath);
            } catch (\Throwable $e) {
                unlink($tempFilePath);
                throw new MineException($e->getMessage());
            }
        } else {
            return false;
        }
        if ($closure instanceof \Closure) {
            return $closure($model, $data);
        }

        foreach ($data as $datum) {
            $model::create($datum);
        }
        return true;
    }

    /**
     * 导出
     * @param string $filename
     * @param array|\Closure $closure
     * @return \Psr\Http\Message\ResponseInterface
     * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
     * @throws \Psr\Container\ContainerExceptionInterface
     * @throws \Psr\Container\NotFoundExceptionInterface
     */
    public function export(string $filename, array|\Closure $closure, \Closure $callbackData = null, array $config = []): \Psr\Http\Message\ResponseInterface
    {
        if (!empty($config['dataTemplateFile'] ?? '') && file_exists($config['dataTemplateFile'])){
            $spread = IOFactory::load($config['dataTemplateFile']);
        }else{
            $spread = new Spreadsheet();
        }

        $sheet  = $spread->getActiveSheet();
        $filename .= '.xlsx';

        is_array($closure) ? $data = &$closure : $data = $closure();

        //开始行
        $row        = (int)($config['startRow'] ?? 1);

        // 表头
        $titleStart = 0;
        foreach ($this->property as $item) {
            $headerColumn = $this->getColumnIndex($titleStart) . $row;
            $sheet->setCellValue($headerColumn, $item['value']);
            $style = $sheet->getStyle($headerColumn)->getFont()->setBold(true);
            $columnDimension = $sheet->getColumnDimension($headerColumn[0]);

            empty($item['width']) ? $columnDimension->setAutoSize(true) : $columnDimension->setWidth((float) $item['width']);

            empty($item['align']) || $sheet->getStyle($headerColumn)->getAlignment()->setHorizontal($item['align']);

            empty($item['headColor']) || $style->setColor(new Color(str_replace('#', '', $item['headColor'])));

            if (!empty($item['headBgColor'])) {
                $sheet->getStyle($headerColumn)->getFill()
                    ->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)
                    ->getStartColor()->setARGB(str_replace('#', '', $item['headBgColor']));
            }
            $titleStart++;
        }

        $generate           = $this->yieldExcelData($data);

        //开始合并列
        $startMergeRows     = [];

        // 表体
        try {
            $row ++;
            while ($generate->valid()) {
                $column = 0;
                $items = $generate->current();
                
                $mergeRows          = $items['mergeRows'] ?? 0;
                if (isset($items['mergeRows'])) unset($items['mergeRows']);

                foreach ($items as $name => $value) {
                    $columnRow = $this->getColumnIndex($column) . $row;
                    $annotation = '';
                    foreach ($this->property as $item) {
                        if ($item['name'] == $name) {
                            $annotation = $item;
                            break;
                        }
                    }

                    //wyqing-扩展新增-2024-04-16---------start
                    if (!empty($annotation['optionsString'])){
                        $objValidation = $sheet->getCell($columnRow)->getDataValidation(); //这一句为要设置数据有效性的单元格
                        $objValidation -> setType(DataValidation::TYPE_LIST)
                        -> setErrorStyle(DataValidation::STYLE_STOP)
                        -> setAllowBlank(true)
                        -> setShowInputMessage(true)
                        -> setShowErrorMessage(true)
                        -> setShowDropDown(true)
                        -> setErrorTitle('输入的值有误')
                        -> setError('您输入的值不在下拉框列表内')
                        -> setPromptTitle('')
                        -> setPrompt('')
                        -> setOperator(DataValidation::OPERATOR_BETWEEN)
                        -> setFormula1('"'.$annotation['optionsString'].'"');
                    }
                    //wyqing-扩展新增-2024-04-16---------end

                    if (!empty($annotation['dictName'])) {
                        $sheet->setCellValue($columnRow, $annotation['dictName'][$value]);
                    } else if (!empty($annotation['path'])){
                        $sheet->setCellValue($columnRow, data_get($items, $annotation['path']));
                    } else if (!empty($annotation['dictData'])) {
                        $sheet->setCellValue($columnRow, $annotation['dictData'][$value]);
                    } else if(!empty($this->dictData[$name])){
                        $sheet->setCellValue($columnRow, $this->dictData[$name][$value] ?? '');
                    } else {
                        $sheet->setCellValue($columnRow, $value . "\t");
                    }

                    if (! empty($item['color'])) {
                        $sheet->getStyle($columnRow)->getFont()
                            ->setColor(new Color(str_replace('#', '', $annotation['color'])));
                    }

                    if (! empty($item['bgColor'])) {
                        $sheet->getStyle($columnRow)->getFill()
                            ->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)
                            ->getStartColor()->setARGB(str_replace('#', '', $annotation['bgColor']));
                    }

                    //兼容行合并
                    if ($annotation['isMergeRows'] === 1 && $mergeRows > 0){
                        //存储开始合并的第一行
                        if (!isset($startMergeRows[$this->getColumnIndex($column) . '-' .$value])){
                            $startMergeRows[$this->getColumnIndex($column) . '-' .$value] = $row;
                        }

                        //合并 结束行
                        if ($columnRow === $this->getColumnIndex($column) . (($startMergeRows[$this->getColumnIndex($column) . '-' .$value] ?? 0) + $mergeRows - 1)){
                            $sheet->mergeCells($this->getColumnIndex($column) . $startMergeRows[$this->getColumnIndex($column) . '-' .$value] . ':' . $columnRow);
                            $style = [
                                'alignment' => [
                                    //'horizontal' => Alignment::HORIZONTAL_CENTER, // 水平居中
                                    'vertical' => Alignment::VERTICAL_CENTER,    // 垂直居中
                                ]
                            ];
                            $sheet->getStyle($this->getColumnIndex($column) . $startMergeRows[$this->getColumnIndex($column) . '-' .$value])->applyFromArray($style);
                        }
                    }
                    
                    $column++;
                }
                $generate->next();
                $row++;
            }
        } catch (\RuntimeException $e) {}

        $writer = IOFactory::createWriter($spread, 'Xlsx');
        ob_start();

        //兼容文件保存指定路径
        $out    = 'php://output';

        $path   = (isset($config['path']) && !empty($config['path'])) ? rtrim($path, '/') . '/' : '';
        if (!empty($path) && is_dir($path)) {
            $out = $path . $filename;
        }

        $writer->save($out);
        $res = $this->downloadExcel($filename, ob_get_contents());
        ob_end_clean();

        $spread->disconnectWorksheets();

        return $res;
    }

    protected function yieldExcelData(array &$data): \Generator
    {
        foreach ($data as $dat) {
            $yield = [];
            foreach ($this->property as $item) {
                $yield[ $item['name'] ] = $dat[$item['name']] ?? '';

                //兼容合并行
                if (($dat['mergeRows'] ?? 0) > 0){
                    $yield['mergeRows'] = $dat['mergeRows'] ?? 0;
                }
            }
            yield $yield;
        }
    }
}